/* fiofuncs.c - formatted I/O library */

/*
DESCRIPTION
This library provides the basic formatting and scanning I/O functions.  It
includes some routines from the ANSI-compliant printf()/scanf()
family of routines.  It also includes several utility routines.

If the floating-point format specifications `e', `E', `f', `g', and `G' are
to be used with these routines, the routine floatInit() must be called
first.  If the configuration macro INCLUDE_FLOATING_POINT is defined,
floatInit() is called by the root task, usrRoot(), in usrConfig.c.

These routines do not use the buffered I/O facilities provided by the
standard I/O facility.  Thus, they can be invoked even if the standard I/O
package has not been included.  This includes printf(), which in most UNIX
systems is part of the buffered standard I/O facilities.  Because printf()
is so commonly used, it has been implemented as an unbuffered I/O function.
This allows minimal formatted I/O to be achieved without the overhead of
the entire standard I/O package.  For more information, see the manual
entry for ansiStdio.
*/

#include "ctype.h"
#include "errno.h"
#include "string.h"
#include "stdarg.h"
#include "limits.h"
#include "stdlib.h"
#include "floatio.h"


/* Macros for converting digits to letters and vice versa */

#define	BUF		400		/* buffer for %dfg etc */
#define	PADSIZE		16		/* pad chunk size */

#define	to_digit(c)	((c) - '0')
#define is_digit(c)	((unsigned)to_digit(c) <= 9)
#define	to_char(n)	((n) + '0')

#define	PAD(howmany, with) 					\
    { 								\
    if ((n = (howmany)) > 0) 					\
	{ 							\
	while (n > PADSIZE)					\
	    {							\
	    if ((*outRoutine) (with, PADSIZE, outarg) != OK)	\
		return (ERROR);					\
	    n -= PADSIZE; 					\
	    }							\
	if ((*outRoutine) (with, n, outarg) != OK)		\
	    return (ERROR);					\
	}							\
    }

/* to extend shorts, signed and unsigned arg extraction methods are needed */
#define	SARG()	((doLongLongInt) ? (long long) va_arg(vaList, long long) : \
		 (doLongInt) ? (long long)(long)va_arg(vaList, long) : \
		 (doShortInt) ? (long long)(short)va_arg(vaList, int) : \
		 (long long)(int) va_arg(vaList, int))

#define	UARG()	((doLongLongInt) ? (unsigned long long) va_arg(vaList, unsigned long long) : 	\
	 (doLongInt) ? (unsigned long long)(ulong_t)va_arg(vaList,ulong_t):\
	 (doShortInt) ? (unsigned long long)(ushort_t)va_arg(vaList,int):\
	 (unsigned long long)(uint_t) va_arg(vaList, uint_t))


#define GET_CHAR(ch, ix)        ((ix)++, (ch) = (* getRtn) (getArg, -1))

#define ll 		1		/* long long format */

/* globals */

/* The signofnum indicates whether the sign should be included
 * in the precision of a number.
 */

BOOL signofnum = TRUE;


/* locals */
extern int fformat(va_list * ,int ,BOOL ,int ,BOOL * ,char * ,char *);
extern int fscan(FAST int *,int,int,FAST FUNCPTR,FAST int,int *,int *);

LOCAL FUNCPTR fioFltFormatRtn = fformat;
LOCAL FUNCPTR fioFltScanRtn = fscan;

/* Choose PADSIZE to trade efficiency vs size.  If larger printf fields occur
 * frequently, increase PADSIZE (and make the initialisers below longer).
 */

LOCAL char blanks[PADSIZE] =
    {
    ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '
    };

LOCAL char zeroes[PADSIZE] =
    {
    '0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'
    };


/* forwards */

LOCAL int    getbuf (char ** str);
LOCAL STATUS putbuf (char *inbuf, int length, char ** outptr);

LOCAL BOOL scanField (const char **ppFmt, void *pReturn, FUNCPTR getRtn,
		      void* getArg, BOOL *pSuppress, int *pCh, int *pNchars);
LOCAL BOOL scanChar (char *pResult, int fieldwidth, FUNCPTR getRtn,
		     void* getArg, int *pCh, int *pNchars);
LOCAL BOOL scanString (char *pResult, int fieldwidth, FUNCPTR getRtn,
		       void* getArg, int *pCh, int *pNchars);
LOCAL BOOL scanCharSet (char *pResult, int fieldwidth,
			const char *charsAllowed, FUNCPTR getRtn,
			void* getArg, int *pCh, int *pNchars);
LOCAL BOOL scanNum (int base, void *pReturn, int returnSpec, int fieldwidth,
		    FUNCPTR getRtn, void* getArg, int *pCh, int *pNchars);
int fioformatstr(FAST const char *fmt,va_list	vaList,FUNCPTR	outRoutine, void* outarg);
int fioscans(const char *fmt,FUNCPTR getRtn,void* getArg,int* pUnget,va_list vaList);




/*******************************************************************************
*
* sprintf - write a formatted string to a buffer (ANSI)
*
* This routine copies a formatted string to a specified buffer, which is
* null-terminated.  Its function and syntax are otherwise identical
* to printf().
*
* RETURNS:
* The number of characters copied to <buffer>, not including the NULL
* terminator.
*
* SEE ALSO: printf(),
* .I "American National Standard for Information Systems -"
* .I "Programming Language - C, ANSI X3.159-1989: Input/Output (stdio.h)"
*
* VARARGS2
*/

int sprintf
    (
    char *  buffer,	/* buffer to write to */
    const char *  fmt,	/* format string */
    ...			/* optional arguments to format */
    )
    {
    va_list	vaList;		/* traverses argument list */
    int		nChars;

    va_start (vaList, fmt);
    nChars = fioformatstr (fmt, vaList, putbuf, (void*) &buffer);
    va_end (vaList);

    *buffer = EOS;

    return (nChars);
    }



/*******************************************************************************
*
* vsprintf - write a string formatted with a variable argument list to a buffer (ANSI)
*
* This routine copies a string formatted with a variable argument list to
* a specified buffer.  This routine is identical to sprintf(), except that it
* takes the variable arguments to be formatted as a list <vaList> of type
* `va_list' rather than as in-line arguments.
*
* RETURNS:
* The number of characters copied to <buffer>, not including the NULL
* terminator.
*
* SEE ALSO: sprintf(),
* .I "American National Standard for Information Systems -"
* .I "Programming Language - C, ANSI X3.159-1989: Input/Output (stdio.h)"
*/

int vsprintf
    (
    char *	  buffer,	/* buffer to write to */
    const char *  fmt,		/* format string */
    va_list	  vaList	/* optional arguments to format */
    )
    {
    int nChars;

    nChars = fioformatstr (fmt, vaList, putbuf, (void*) &buffer);
    *buffer = EOS;

    return (nChars);
    }


/*******************************************************************************
*
* fioformatstr - convert a format string
*
* This routine is used by the printf() family of routines to handle the
* actual conversion of a format string.  The first argument is a format
* string, as described in the entry for printf().  The second argument is a
* variable argument list <vaList> that was previously established.
*
* As the format string is processed, the result will be passed to the output
* routine whose address is passed as the third parameter, <outRoutine>.
* This output routine may output the result to a device, or put it in a
* buffer.  In addition to the buffer and length to output, the fourth
* argument, <outarg>, will be passed through as the third parameter to the
* output routine.  This parameter could be a file descriptor, a buffer
* address, or any other value that can be passed in an "void*".
*
* The output routine should be declared as follows:
* .CS
*     STATUS outRoutine
*         (
*         char *buffer, /@ buffer passed to routine            @/
*         int  nchars,  /@ length of buffer                    @/
*         void*  outarg /@ arbitrary arg passed to fmt routine @/
*         )
* .CE
* The output routine should return OK if successful, or ERROR if unsuccessful.
*
* RETURNS:
* The number of characters output, or ERROR if the output routine
* returned ERROR.
*
* INTERNAL
* Warning, this routine is extremely complex and its integrity is easily
* destroyed. Do not change this code without absolute understanding of all
* ramifications and consequences.
*/
int fioformatstr
    (
    FAST const char *fmt, 	/* format string */
    va_list	vaList,       	/* pointer to varargs list */
    FUNCPTR	outRoutine,   	/* handler for args as they're formatted */
    void* outarg      		/* argument to routine */
    )
    {
    FAST int	ch;		/* character from fmt */
    FAST int	n;		/* handy integer (short term usage) */
    FAST char *	cp;		/* handy char pointer (short term usage) */
    int		width;		/* width from format (%8d), or 0 */
    char	sign;		/* sign prefix (' ', '+', '-', or \0) */
    unsigned long ulongLongVal; 	/* unsigned 64 bit arguments %[diouxX] */
    int		prec;		/* precision from format (%.3d), or -1 */
    int		oldprec;	/* old precision from format (%.3d), or -1 */
    int		dprec;		/* a copy of prec if [diouxX], 0 otherwise */
    int		fpprec;		/* `extra' floating precision in [eEfgG] */
    int		size;		/* size of converted field or string */
    int		fieldsz;	/* field size expanded by sign, etc */
    int		realsz;		/* field size expanded by dprec */

    char	FMT[20];	/* To collect fmt info */
    FAST char *	Collect;	/* char pointer to FMT */

    BOOL	doLongInt;	/* long integer */
    BOOL	doLongLongInt;	/* long long integer - 64 bit */
    BOOL	doShortInt;	/* short integer */
    BOOL	doAlt;		/* alternate form */
    BOOL	doLAdjust;	/* left adjustment */
    BOOL	doZeroPad;	/* zero (as opposed to blank) pad */
    BOOL	doHexPrefix;	/* add 0x or 0X prefix */
    BOOL	doSign;		/* change sign to '-' */
    char	buf[BUF];	/* space for %c, %[diouxX], %[eEfgG] */
//#if (CPU_FAMILY != AM29XXX)
    char	ox[2];		/* space for 0x hex-prefix */
//#else
//    char	ox[3];		/* space for 0x hex-prefix */
//#endif /*(CPU_FAMILY != AM29XXX)*/
    char *	xdigs = NULL;	/* digits for [xX] conversion */
    int		ret = 0;	/* return value accumulator */
    enum {OCT, DEC, HEX} base;	/* base for [diouxX] conversion */


    FOREVER		/* Scan the format for conversions (`%' character) */
	{
	for (cp = CHAR_FROM_CONST(fmt);((ch=*fmt) != EOS)&&(ch != '%'); fmt++)
	    ;

	if ((n = fmt - cp) != 0)
	    {
	    if ((*outRoutine) (cp, n, outarg) != OK)
		return (ERROR);

	    ret += n;
	    }

	if (ch == EOS)
	    return (ret);		/* return total length */

	fmt++;				/* skip over '%' */

	*FMT		= EOS;
	Collect		= FMT;
	doLongInt	= FALSE;	/* long integer */
	doLongLongInt	= FALSE;	/* 64 bit integer */
	doShortInt	= FALSE;	/* short integer */
	doAlt		= FALSE;	/* alternate form */
	doLAdjust	= FALSE;	/* left adjustment */
	doZeroPad	= FALSE;	/* zero (as opposed to blank) pad */
	doHexPrefix	= FALSE;	/* add 0x or 0X prefix */
	doSign		= FALSE;	/* change sign to '-' */
	dprec		= 0;
	fpprec		= 0;
	width		= 0;
	prec		= -1;
	oldprec		= -1;
	sign		= EOS;

#define get_CHAR  (ch = *Collect++ = *fmt++)


rflag:
get_CHAR;
reswitch:
	switch (ch)
	    {
	    case ' ':
		    /* If the space and + flags both appear, the space
		     * flag will be ignored. -- ANSI X3J11
		     */
		    if (!sign)
			sign = ' ';
		    goto rflag;
	    case '#':
		    doAlt = TRUE;
		    goto rflag;

	    case '*':
		    /* A negative field width argument is taken as a
		     * flag followed by a positive field width.
		     *	-- ANSI X3J11
		     * They don't exclude field widths read from args.
		     */
		    if ((width = va_arg(vaList, int)) >= 0)
			goto rflag;

		    width = -width;			/* FALLTHROUGH */

	    case '-':
		    doLAdjust = TRUE;
		    goto rflag;

	    case '+':
		    sign = '+';
		    goto rflag;

	    case '.':
		    get_CHAR;
		    if ( ch == '*' )
			{
			n = va_arg(vaList, int);
			prec = (n < 0) ? -1 : n;
			goto rflag;
			}

		    n = 0;
		    while (is_digit(ch))
			{
			n = 10 * n + to_digit(ch);
			get_CHAR;
			}
		    prec = n < 0 ? -1 : n;
		    goto reswitch;

	    case '0':
		    /* Note that 0 is taken as a flag, not as the
		     * beginning of a field width. -- ANSI X3J11
		     */
		    doZeroPad = TRUE;
		    goto rflag;

	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		    n = 0;
		    do
			{
			n = 10 * n + to_digit(ch);
			get_CHAR;
			} while (is_digit(ch));

		    width = n;
		    goto reswitch;

	    case 'h':
		    doShortInt = TRUE;
		    goto rflag;

	    case 'l':
		    get_CHAR;
		    if ( ch == 'l' )
			{
			doLongLongInt = TRUE;
			goto rflag;
			}
		    else
			{
		        doLongInt = TRUE;
		        goto reswitch;
			}

	    case 'c':
			*(cp = buf) = va_arg(vaList, int);
			size = 1;
			sign = EOS;
		    break;

	    case 'D':
		    doLongInt = TRUE; 			/* FALLTHROUGH */

	    case 'd':
	    case 'i':

		    ulongLongVal = SARG();
		    if ((long long)ulongLongVal < 0)
			{
			ulongLongVal = -ulongLongVal;
			sign = '-';
			}
		    base = DEC;
		    goto number;

	    case 'n':
		    /* ret is int, so effectively %lln = %ln */
		    if (doLongLongInt)
			*va_arg(vaList, long long *) = (long long) ret;
		    else if (doLongInt)
			*va_arg(vaList, long *) = ret;
		    else if (doShortInt)
			*va_arg(vaList, short *) = ret;
		    else
			*va_arg(vaList, int *) = ret;
		    continue;				/* no output */

	    case 'O':
		    doLongInt = TRUE;			/* FALLTHROUGH */

	    case 'o':
		    ulongLongVal = UARG();
		    base = OCT;
		    goto nosign;

	    case 'p':
		    /* The argument shall be a pointer to void.  The
		     * value of the pointer is converted to a sequence
		     * of printable characters, in an implementation
		     * defined manner. -- ANSI X3J11
		     */
		    ulongLongVal = (unsigned long long) (unsigned int)
			           va_arg(vaList, void *);/* NOSTRICT */
		    base	= HEX;
		    xdigs	= "0123456789abcdef";
		    doHexPrefix = TRUE;
		    ch		= 'x';
		    goto nosign;

	    case 's':
		    if ((cp = va_arg(vaList, char *)) == NULL)
			cp = "(null)";

		    if (prec >= 0)
			{
			/* can't use strlen; can only look for the
			 * NUL in the first `prec' characters, and
			 * strlen() will go further.
			 */

			char *p = (char *)memchr(cp, 0, prec);

			if (p != NULL)
			    {
			    size = p - cp;
			    if (size > prec)
				size = prec;
			    }
			else
			    size = prec;
			}
		    else
			size = strlen(cp);

		    sign = EOS;
		    break;

	    case 'U':
		    doLongInt = TRUE;			/* FALLTHROUGH */

	    case 'u':
		    ulongLongVal = UARG();
		    base = DEC;
		    goto nosign;

	    case 'X':
		    xdigs = "0123456789ABCDEF";
		    goto hex;

	    case 'x':
		    xdigs = "0123456789abcdef";

hex:
		    ulongLongVal = UARG();
		    base = HEX;

		    /* leading 0x/X only if non-zero */

		    if (doAlt && (ulongLongVal != 0))
			doHexPrefix = TRUE;

		    /* unsigned conversions */
nosign:		    sign = EOS;

		    /* ... diouXx conversions ... if a precision is
		     * specified, the 0 flag will be ignored. -- ANSI X3J11
		     */

number:		    if ((dprec = prec) >= 0)
			doZeroPad = FALSE;

		    /* The result of converting a zero value with an
		     * explicit precision of zero is no characters.
		     * -- ANSI X3J11
		     */
		    cp = buf + BUF;
		    if ((ulongLongVal != 0) || (prec != 0))
			{
			/* unsigned mod is hard, and unsigned mod
			 * by a constant is easier than that by
			 * a variable; hence this switch.
			 */
			switch (base)
			    {
			    case OCT:
				do
				    {
				    *--cp = to_char(ulongLongVal & 7);
				    ulongLongVal >>= 3;
				    } while (ulongLongVal);

				/* handle octal leading 0 */

				if (doAlt && (*cp != '0'))
				    *--cp = '0';
				break;

			    case DEC:
				/* many numbers are 1 digit */

				while (ulongLongVal >= 10)
				    {
				    *--cp = to_char(ulongLongVal % 10);
				    ulongLongVal /= 10;
				    }

				*--cp = to_char(ulongLongVal);
				break;

			    case HEX:
				do
				    {
				    *--cp = xdigs[ulongLongVal & 15];
				    ulongLongVal >>= 4;
				    } while (ulongLongVal);
				break;

			    default:
				cp = "bug in vfprintf: bad base";
				size = strlen(cp);
				goto skipsize;
			    }
			}

		    size = buf + BUF - cp;
skipsize:
		    break;

	    case 'L':
		    /* NOT IMPLEMENTED */
		    goto rflag;

	    case 'e':
	    case 'E':
	    case 'f':
	    case 'g':
	    case 'G':
		    if (fioFltFormatRtn != NULL)
			{
			oldprec = prec;		/* in case of strange float */
			if (prec > MAXFRACT) 	/* do realistic precision */
			    {
			    if (((ch != 'g') && (ch != 'G')) || doAlt)
				fpprec = prec - MAXFRACT;
			    prec = MAXFRACT;	/* they asked for it! */
			    }
			else if (prec == -1)
			    prec = 6;		/* ANSI default precision */

			cp  = buf;		/* where to fill in result */
			*cp = EOS;		/* EOS terminate just in case */

			size = fformat (&vaList,prec,doAlt,ch, &doSign,cp,buf+sizeof(buf));
			if ((int)size < 0)	/* strange value (Nan,Inf,..) */
			    {
			    size = -size;	/* get string length */
			    prec = oldprec;	/* old precision (not default)*/

			    doZeroPad = FALSE;	/* don't pad with zeroes */
			    if (doSign)		/* is strange value signed? */
				sign = '-';
			    }
			else
			    {
			    if (doSign)
				sign = '-';

			    if (*cp == EOS)
				cp++;
			    }
			break;
			}
		    /* FALLTHROUGH if no floating point format routine */

	    default:			/* "%?" prints ?, unless ? is NULL */
		    if (ch == EOS)
			return (ret);

		    /* pretend it was %c with argument ch */

		    cp   = buf;
		    *cp  = ch;
		    size = 1;
		    sign = EOS;
		    break;
	    }

	/* All reasonable formats wind up here.  At this point,
	 * `cp' points to a string which (if not doLAdjust)
	 * should be padded out to `width' places.  If
	 * doZeroPad, it should first be prefixed by any
	 * sign or other prefix; otherwise, it should be blank
	 * padded before the prefix is emitted.  After any
	 * left-hand padding and prefixing, emit zeroes
	 * required by a decimal [diouxX] precision, then print
	 * the string proper, then emit zeroes required by any
	 * leftover floating precision; finally, if doLAdjust,
	 * pad with blanks.
	 */

	/*
	 * compute actual size, so we know how much to pad.
	 * fieldsz excludes decimal prec; realsz includes it
	 */

	fieldsz = size + fpprec;

	if (sign)
	    {
	    fieldsz++;
	    if (signofnum)
	        dprec++;
	    }
	else if (doHexPrefix)
	    fieldsz += 2;

	realsz = (dprec > fieldsz) ? dprec : fieldsz;

	/* right-adjusting blank padding */

	if (!doLAdjust && !doZeroPad)
	    PAD(width - realsz, blanks);

	/* prefix */

	if (sign)
	    {
	    if ((*outRoutine) (&sign, 1, outarg) != OK)
		return (ERROR);
	    }
	else if (doHexPrefix)
	    {
	    ox[0] = '0';
	    ox[1] = ch;
	    if ((*outRoutine) (ox, 2, outarg) != OK)
		return (ERROR);
	    }

	/* right-adjusting zero padding */

	if (!doLAdjust && doZeroPad)
	    PAD(width - realsz, zeroes);

	/* leading zeroes from decimal precision */

	PAD(dprec - fieldsz, zeroes);

	/* the string or number proper */

	if ((*outRoutine) (cp, size, outarg) != OK)
	    return (ERROR);

	/* trailing floating point zeroes */

	PAD(fpprec, zeroes);

	/* left-adjusting padding (always blank) */

	if (doLAdjust)
	    PAD(width - realsz, blanks);

	/* finally, adjust ret */

	ret += (width > realsz) ? width : realsz;
	}
    }

/*******************************************************************************
*
* putbuf - put characters in a buffer
*
* This routine is a support routine for sprintf().
* This routine copies length bytes from source to destination, leaving the
* destination buffer pointer pointing at byte following block copied.
*/

LOCAL STATUS putbuf
    (
    char *inbuf,                /* pointer to source buffer */
    int length,                 /* number of bytes to copy */
    char **outptr               /* pointer to destination buffer */
    )
    {
    bcopy (inbuf, *outptr, length);
    *outptr += length;

    return (OK);
    }


/*******************************************************************************
*
* sscanf - read and convert characters from an ASCII string (ANSI)
*
* This routine reads characters from the string <str>, interprets them
* according to format specifications in the string <fmt>, which specifies
* the admissible input sequences and how they are to be converted for
* assignment, using subsequent arguments as pointers to the objects to
* receive the converted input.
*
* If there are insufficient arguments for the format, the behavior is
* undefined.  If the format is exhausted while arguments remain, the excess
* arguments are evaluated but are otherwise ignored.
*
* The format is a multibyte character sequence, beginning and ending in
* its initial shift state.  The format is composed of zero or more directives:
* one or more white-space characters; an ordinary multibyte character (neither
* `%' nor a white-space character); or a conversion specification.  Each
* conversion specification is introduced by the `%' character.  After the `%',
* the following appear in sequence:
*
* .iP "" 4
* An optional assignment-suppressing character `*'.
* .iP
* An optional non-zero decimal integer that specifies the maximum field
* width.
* .iP
* An optional `h', `l' (ell) or `ll' (ell-ell) indicating the size of the
* receiving object.  The conversion specifiers `d', `i', and `n' should
* be preceded by `h' if the corresponding argument is a pointer to `short
* int' rather than a pointer to `int', or by `l' if it is a pointer to
* `long int', or by `ll' if it is a pointer to `long long int'.  Similarly,
* the conversion specifiers `o', `u', and `x' shall be preceded by `h' if
* the corresponding argument is a pointer to `unsigned short int' rather
* than a pointer to `unsigned int', or by `l' if it is a pointer to
* `unsigned long int', or by `ll' if it is a pointer to `unsigned long
* long int'.  Finally, the conversion specifiers `e', `f', and `g' shall
* be preceded by `l' if the corresponding argument is a pointer to
* `double' rather than a pointer to `float'.  If a `h', `l' or `ll'
* appears with any other conversion specifier, the behavior is undefined.
* .iP
* \&WARNING: ANSI C also specifies an optional `L' in some of the same
* contexts as `l' above, corresponding to a `long double *' argument.
* However, the current release of the VxWorks libraries does not support
* `long double' data; using the optional `L' gives unpredictable results.
* .iP
* A character that specifies the type of conversion to be applied.  The
* valid conversion specifiers are described below.
* .LP
*
* The sscanf() routine executes each directive of the format in turn.  If a
* directive fails, as detailed below, sscanf() returns.  Failures
* are described as input failures (due to the unavailability of input
* characters), or matching failures (due to inappropriate input).
*
* A directive composed of white-space character(s) is executed by reading
* input up to the first non-white-space character (which remains unread),
* or until no more characters can be read.
*
* A directive that is an ordinary multibyte character is executed by reading
* the next characters of the stream.  If one of the characters differs from
* one comprising the directive, the directive fails, and the differing and
* subsequent characters remain unread.
*
* A directive that is a conversion specification defines a set of matching
* input sequences, as described below for each specifier.  A conversion
* specification is executed in the following steps:
*
* Input white-space characters (as specified by the isspace() function) are
* skipped, unless the specification includes a `[', `c', or `n' specifier.
*
* An input item is read from the stream, unless the specification includes
* an `n' specifier.  An input item is defined as the longest matching
* sequence of input characters, unless that exceeds a specified field width,
* in which case it is the initial subsequence of that length in the
* sequence.  The first character, if any, after the input item remains
* unread.  If the length of the input item is zero, the execution of the
* directive fails:  this condition is a matching failure, unless an error
* prevented input from the stream, in which case it is an input failure.
*
* Except in the case of a `%' specifier, the input item is converted to a
* type appropriate to the conversion specifier.  If the input item is not a
* matching sequence, the execution of the directive fails:  this condition
* is a matching failure.  Unless assignment suppression was indicated by a
* `*', the result of the conversion is placed in the object pointed to by
* the first argument following the <fmt> argument that has not already
* received a conversion result.  If this object does not have an appropriate
* type, or if the result of the conversion cannot be represented in the
* space provided, the behavior is undefined.
*
* The following conversion specifiers are valid:
*
* .iP `d'
* Matches an optionally signed decimal integer whose format is
* the same as expected for the subject sequence of the strtol()
* function with the value 10 for the <base> argument.  The
* corresponding argument should be a pointer to `int'.
* .iP `i'
* Matches an optionally signed integer, whose format is the
* same as expected for the subject sequence of the strtol()
* function with the value 0 for the <base> argument.  The
* corresponding argument should be a pointer to `int'.
* .iP `o'
* Matches an optionally signed octal integer, whose format is the
* same as expected for the subject sequence of the strtoul()
* function with the value 8 for the <base> argument.  The
* corresponding argument should be a pointer to `unsigned int'.
* .iP `u'
* Matches an optionally signed decimal integer, whose format is
* the same as expected for the subject sequence of the strtoul()
* function with the value 10 for the <base> argument.  The
* corresponding argument should be a pointer to `unsigned int'.
* .iP `x'
* Matches an optionally signed hexadecimal integer, whose format is
* the same as expected for the subject sequence of the strtoul()
* function with the value 16 for the <base> argument.  The
* corresponding argument should be a pointer to `unsigned int'.
* .iP "`e', `f', `g'"
* Match an optionally signed floating-point number, whose format
* is the same as expected for the subject string of the strtod()
* function.  The corresponding argument should be a pointer to `float'.
* .iP `s'
* Matches a sequence of non-white-space characters.  The
* corresponding argument should be a pointer to the initial
* character of an array large enough to accept the sequence
* and a terminating null character, which will be added
* automatically.
* .iP `['
* Matches a non-empty sequence of characters from a set of
* expected characters (the `scanset').  The corresponding argument
* should be a pointer to the initial character of an array large
* enough to accept the sequence and a terminating null character,
* which is added automatically.  The conversion specifier
* includes all subsequent character in the format string, up to
* and including the matching right bracket (`]').  The characters
* between the brackets (the `scanlist') comprise the scanset,
* unless the character after the left bracket is a circumflex (`^')
* in which case the scanset contains all characters that do not
* appear in the scanlist between the circumflex and the right
* bracket.  If the conversion specifier begins with "[]" or "[^]", the
* right bracket character is in the scanlist and the next
* right bracket character is the matching right bracket that ends
* the specification; otherwise the first right bracket character
* is the one that ends the specification.
* .iP `c'
* Matches a sequence of characters of the number specified by the
* field width (1 if no field width is present in the directive).
* The corresponding argument should be a pointer to the initial
* character of an array large enough to accept the sequence.
* No null character is added.
* .iP `p'
* Matches an implementation-defined set of sequences, which should be
* the same as the set of sequences that may be produced by the %p
* conversion of the fprintf() function.  The corresponding argument
* should be a pointer to a pointer to `void'.  VxWorks defines its
* pointer input field to be consistent with pointers written by the
* fprintf() function ("0x" hexadecimal notation).  If the input item is
* a value converted earlier during the same program execution, the
* pointer that results should compare equal to that value; otherwise
* the behavior of the %p conversion is undefined.
* .iP `n'
* No input is consumed.  The corresponding argument should be a pointer to
* `int' into which the number of characters read from the input stream so
* far by this call to sscanf() is written.  Execution of a %n directive does
* not increment the assignment count returned when sscanf() completes
* execution.
* .iP `%'
* Matches a single `%'; no conversion or assignment occurs.  The
* complete conversion specification is %%.
* .LP
*
* If a conversion specification is invalid, the behavior is undefined.
*
* The conversion specifiers `E', `G', and `X' are also valid and behave the
* same as `e', `g', and `x', respectively.
*
* If end-of-file is encountered during input, conversion is terminated.  If
* end-of-file occurs before any characters matching the current directive
* have been read (other than leading white space, where permitted), execution
* of the current directive terminates with an input failure; otherwise, unless
* execution of the current directive is terminated with a matching failure,
* execution of the following directive (if any) is terminated with an input
* failure.
*
* If conversion terminates on a conflicting input character, the offending
* input character is left unread in the input stream.  Trailing white space
* (including new-line characters) is left unread unless matched by a
* directive.  The success of literal matches and suppressed assignments is
* not directly determinable other than via the %n directive.
*
* INCLUDE FILES: fioLib.h
*
* RETURNS:
* The number of input items assigned, which can be fewer than provided for,
* or even zero, in the event of an early matching failure; or EOF if an
* input failure occurs before any conversion.
*
* SEE ALSO: fscanf(), scanf(),
* .I "American National Standard for Information Systems -"
* .I "Programming Language - C, ANSI X3.159-1989: Input/Output (stdio.h)"
*
* VARARGS2
*/

int sscanf
    (
    const char *  str,	/* string to scan */
    const char *  fmt,	/* format string */
    ...      		/* optional arguments to format string */
    )
    {
    int		nArgs;
    int		unget;
    va_list	vaList;			/* vararg list */

    va_start (vaList, fmt);
    nArgs = fioscans (fmt, getbuf, &str, &unget, vaList);
    va_end (vaList);

    return (nArgs);
    }

/********************************************************************************
* getbuf - sscanf() support routine: get next character in string
*
* RETURNS: Next character or EOF if empty.
*/

LOCAL int getbuf
    (
    char **str
    )
    {
    int c = (int)(**str);

    if (c == EOS)
        return (EOF);

    ++*str;

    return (c);
    }

/******************************************************************************
*
* fioscans - scan processor
*
* NOMANUAL
*/

int fioscans
    (
    const char *fmt,            /* the format string */
    FUNCPTR     getRtn,         /* routine to get next character */
    void *      getArg,         /* argument to be passed to get */
    int *       pUnget,         /* where to return unget char (-1 = no unget) */
    va_list     vaList          /* vararg list */
    )
    {
    BOOL        suppress;                       /* assignment was suppressed */
    void *      pArg;
    int         ch;
    BOOL        countArg;                       /* FALSE for %n, not counted */
    BOOL        quit            = FALSE;
    int         argsAssigned    = 0;
    int         nchars          = 0;

    pArg = va_arg (vaList, void *);     /* get first pointer arg */

    ch = (* getRtn) (getArg);           /* get first char */


    while (!quit && (*fmt != EOS))
        {
        countArg = TRUE;                /* default = count arg assignment */

        switch (*fmt)
            {
            case ' ':
            case '\n':
            case '\t':                  /* skip white space in string */
                ++fmt;
                while (isspace (ch))
                    GET_CHAR (ch, nchars);
                break;

            case '%':                   /* conversion specification */
                ++fmt;

                /* NOTE: if next char is '%' (i.e. "%%") then a normal match
                 * of '%' is required: FALL THROUGH to default case
                 */
                if (*fmt != '%')
                    {

                    /* Skip white space except for 'c', 'n', '[' specifiers.
                     * Suppress including %n fields in returned count.
                     */


                    switch (*fmt)
                        {
                        case 'c':
                        case '[':
                            break;

                        case '*':  /* ignore the '*' for purposes of white
			            * space removal                        */
			    switch(*(fmt+1))
			        {
				case 'c':
				case '[':
				    break;

                        	default:
                            	    while (isspace (ch))
                                	GET_CHAR (ch, nchars);
				}
			    break;

                        case 'n':
                            countArg = FALSE;           /* we don't count %n */
                            break;

                        case 'h':                       /* %hn, %ln, %Ln, %lln */
			case 'l':
			case 'L':
			    if ((*(fmt + 1) == 'n') ||
			        ((*fmt == 'l') && (*(fmt + 1) == 'l') && (*(fmt + 2) == 'n')))
                                {
			        countArg = FALSE;
			        break;
			        }

                        default:
                            while (isspace (ch))
                                GET_CHAR (ch, nchars);
                        }


                    /* Quit if at end of input string, unless spec is %[hl]n */

                    if (ch == EOF)                      /* if at end of input */                        {
                        switch (*fmt)
                            {
                            case 'n':
                                break;                  /* %n is still valid */

                            case 'h':                   /* also %hn, %ln, %lln */
                            case 'l':
			    case 'L':
				if ((*(fmt + 1) == 'n') ||
			            ((*fmt == 'l') && (*(fmt + 1) == 'l') && (*(fmt + 2) == 'n')))
                                    {
			            break;		/* keep going */
			            }
                                /* FALL THROUGH */

                            default:
                                quit = TRUE;
                                break;
                            }
                        }

                    /* Scan field in input string */

                    if (scanField (&fmt, pArg, getRtn, getArg, &suppress, &ch,
                                   &nchars))
                        {
                        /* successful scan */

                        if (!suppress)
                            {
                            if (countArg)               /* unless arg was %n */
                                ++argsAssigned;         /*  increment count */

                            pArg = va_arg (vaList, void *);     /* next arg */
                            }
                        }
                    else
                        quit = TRUE;         	/* unsuccessful */
                    break;
                    }

                /* NOTE: else of above if FALLS THROUGH to default below! */

            default:                   		/* character to match */
                if (ch == *fmt)
                    {
                    GET_CHAR (ch, nchars);      /* advance past matched char */
                    ++fmt;                      /* advance past matched char */
                    }
                else
                    quit = TRUE;                /* match failed */
                break;
            }
        }

    *pUnget = ch;

    if ((argsAssigned == 0) && (ch == EOF))
        return (EOF);

    return (argsAssigned);
    }

/********************************************************************************
* scanField - assign value to argument according to format
*
* RETURNS:
* TRUE if conversion successful, otherwise FALSE;
* <pSuppress> is set TRUE if assignment was suppressed.
*
* NOMANUAL
*/

LOCAL BOOL scanField
    (
    const char **ppFmt,          /* conversion specification string */
    FAST void *  pReturn,        /* where to set result */
    FUNCPTR      getRtn,
    void *       getArg,
    BOOL *       pSuppress,      /* set to TRUE if argptr was not assigned */
    int *        pCh,
    int *        pNchars
    )
    {
    FAST int    fieldwidth = 0;         /* maximum number of chars to convert.
                                         * default 0 implies no limit */
    int         returnSpec = 0;         /* 0, 'h', 'l', ll */
    FAST const char *pFmt = *ppFmt;     /* ptr to current char in fmt string */
    FAST int    base;
    /* check for assignment suppression */

    *pSuppress = (*pFmt == '*');
    if (*pSuppress)
        {
        ++pFmt;
        pReturn = NULL;
        }

    /* check for specification of maximum fieldwidth */

    while (isdigit ((int)*pFmt))
        fieldwidth = 10 * fieldwidth + (*pFmt++ - '0');

    /* check for specification of size of returned value */

    switch (*pFmt)
        {
        case 'h':
	case 'L':
	    returnSpec = *pFmt++;

	    break;
	case 'l':
            if (*(++pFmt) == 'l')
	        {
	        returnSpec = ll;
	        pFmt++;
	        }
	    else
	        returnSpec = 'l';

            break;
        }

    /* set default fieldwidth if not specified */

    if (fieldwidth == 0)
        fieldwidth = (*pFmt == 'c') ? 1 : INT_MAX;


    switch (*pFmt)
        {
        case 'c':               /* character, or character array */

            if (!scanChar ((char *) pReturn, fieldwidth, getRtn, getArg,
                           pCh, pNchars))
                return (FALSE);         /* unsuccessful conversion */
            break;

        case 'i':               /* integer */
        case 'o':               /* octal integer */
        case 'd':               /* decimal integer */
        case 'u':               /* unsigned decimal integer */
        case 'x':               /* hex integer */
        case 'X':               /* hex integer */
        case 'p':               /* pointer */

            switch (*pFmt)
                {
                case 'i':       base = 0;       break;
                case 'o':       base = 8;       break;
                case 'd':       base = 10;      break;
                case 'u':       base = 10;      break;
                case 'x':       base = 16;      break;
                case 'X':       base = 16;      break;
                case 'p':       base = 16;      break;
                default :       base = 0;       break;
                }

            if (!scanNum (base, pReturn, returnSpec, fieldwidth, getRtn,
	 	          getArg, pCh, pNchars))
                return (FALSE);         /* unsuccessful conversion */
            break;

        case 'e':               /* exponential float */
        case 'f':               /* float */
        case 'g':               /* float */
        case 'E':               /* exponential double */
        case 'F':               /* double */
        case 'G':               /* double */

            if ((fioFltScanRtn == NULL) ||
                (!(* fioFltScanRtn) (pReturn, returnSpec, fieldwidth, getRtn,
                                     getArg, pCh, pNchars)))
                return (FALSE);         /* unsuccessful conversion */
            break;

        case 's':               /* string */
            if (!scanString ((char *) pReturn, fieldwidth, getRtn, getArg,
                             pCh, pNchars))
                return (FALSE);         /* unsuccessful conversion */
            break;

        case '[':               /* non-space-delimited string */
            if (!scanCharSet ((char *) pReturn, fieldwidth, pFmt, getRtn,
                              getArg, pCh, pNchars))
                return (FALSE);

            /* get past format specifier string */

            while (*pFmt != ']')
                ++pFmt;
            break;


        case 'n':               /* current number of chars scanned */
            if (pReturn != NULL)
                {
                switch (returnSpec)
                    {
                    case 'h': *((short *) pReturn) = *pNchars; break;
                    case 'l': *((long *) pReturn)  = *pNchars; break;
		    case (ll) : *((long long *) pReturn) = *pNchars; break;
                    default:  *((int *) pReturn)   = *pNchars; break;
                    }
                }
            break;

        default:
            return (FALSE);
        }

    *ppFmt = pFmt + 1;          /* update callers format string pointer */

    return (TRUE);              /* successful conversion */
    }

/********************************************************************************
* scanString - perform conversion of space delineated string
*
* RETURNS: TRUE if successful, FALSE if unsuccessful.
*/

LOCAL BOOL scanString
    (
    FAST char * pResult,
    FAST int    fieldwidth,
    FAST FUNCPTR getRtn,
    FAST void *  getArg,
    int *       pCh,
    int *       pNchars
    )
    {
    FAST int ix = 0;
    FAST int ch = *pCh;

    while (ch != EOF && ix < fieldwidth)
        {
        if (isspace (ch))
            break;

        if (pResult != NULL)
            *pResult++ = (char)ch;

        GET_CHAR (ch, ix);
        }

    if (pResult != NULL)
        *pResult = EOS;

    *pCh = ch;
    *pNchars += ix;

    return (ix != 0);
    }


/********************************************************************************
* scanChar - perform character conversion
*
* RETURNS: TRUE if successful, FALSE if unsuccessful.
*/

LOCAL BOOL scanChar
    (
    FAST char * pResult,
    FAST int    fieldwidth,
    FAST FUNCPTR getRtn,
    FAST void *  getArg,
    FAST int *  pCh,
    FAST int *  pNchars
    )
    {
    FAST int ch = *pCh;
    FAST int ix = 0;

    while (ch != EOF && ix < fieldwidth)
        {
        if (pResult != NULL)
            *(pResult++) = (char) ch;

        GET_CHAR (ch, ix);
        }

    *pCh = ch;
    *pNchars += ix;

    return (ix != 0);
    }

/********************************************************************************
* scanCharSet - perform user-specified string conversion
*
* RETURNS: TRUE if successful, FALSE if unsuccessful.
*
* NOMANUAL
*/

LOCAL BOOL scanCharSet
    (
    FAST char * pResult,
    int         fieldwidth,
    FAST const char *charsAllowed,   /* The chars (not) allowed in the string */
    FAST FUNCPTR getRtn,
    FAST void*  getArg,
    int *       pCh,
    int *       pNchars
    )
    {
#define NUM_ASCII_CHARS 128
    char lastChar = 0;
    char charsAllowedTbl [NUM_ASCII_CHARS];
    FAST BOOL allowed;
    int nx;
    FAST int ix;
    FAST int ch;

    if (*charsAllowed++ != '[')         /* skip past the [ */
        return (FALSE);

    /* chars in table will (not) be allowed */

    if (*charsAllowed == '^')
        {
        allowed = FALSE;
        ++charsAllowed;                 /* skip past ^ */
        }
    else
        allowed = TRUE;

    /* initialize table to (not) allow any chars */

    for (ix = 0; ix < NUM_ASCII_CHARS; ix++)
        charsAllowedTbl [ix] = !allowed;

    /* Check for the first char of the set being '-' or ']', in which case
     * they are not special chars.
     */

    if (*charsAllowed == '-' || *charsAllowed == ']')
        charsAllowedTbl [(int)(*charsAllowed++)] = allowed;

    /* Examine the rest of the set, and put it into the table. */

    for (nx = 0; nx < NUM_ASCII_CHARS; nx++)
        {
        if (*charsAllowed == ']' || *charsAllowed == EOS)
            break;      /* all done */

        /* Check if its of the form x-y.
         * If x<y, then allow all chars in the range.
         * If x>=y, its not a legal range.  Just allow '-'.
         * Also, if the '-' is the last char before the final ']',
         * just allow '-'.
         */
        if (*charsAllowed == '-' &&
            (lastChar < *(charsAllowed+1) && *(charsAllowed+1) != ']'))
            {
            ++charsAllowed;     /* skip - */
            for (ix = lastChar; ix <= *charsAllowed; ix++)
                charsAllowedTbl [ix] = allowed;
            }
        else
            {
            charsAllowedTbl [(int)(*charsAllowed)] = allowed;
            lastChar = *charsAllowed;
            }
        ++charsAllowed;
        }

    /* make sure that ']' appeared */

    if (*charsAllowed != ']')
        return (FALSE);

    /* Copy the appropriate portion of the string */

    ix = 0;
    ch = *pCh;

    while (ch != EOF && ix < fieldwidth)
        {
        if (! charsAllowedTbl [(int)ch])
            break;

        if (pResult != NULL)
            *pResult++ = (char) ch;

        GET_CHAR (ch, ix);
        }

	/* If no characters were copied from the input, then this assignment
		has failed */

	if(ix == 0)
		return (FALSE);
	else
		{

    	if (pResult != NULL)
        	*pResult = EOS;

    	*pCh = ch;
    	*pNchars += ix;

    	return (TRUE);
		}
    }
/********************************************************************************
* scanNum - perform number conversion
*
* RETURNS: TRUE if successful, FALSE if unsuccessful.
*/

LOCAL BOOL scanNum
    (
    int         base,
    FAST void * pReturn,
    int         returnSpec,
    int         fieldwidth,
    FAST FUNCPTR getRtn,
    FAST void * getArg,
    int *       pCh,
    int *       pNchars
    )
    {
    int         dig;                    /* current digit */
    BOOL        neg     = FALSE;        /* negative or positive? */
    long long   num     = 0;            /* scanned number */
    FAST int    ix      = 0;            /* present used width */
    FAST int    ch      = *pCh;         /* current character */

    /* check for sign */

    if (ix < fieldwidth)
        {
        if ((char)ch == '+' || (neg = ((char)ch == '-')))
            GET_CHAR (ch, ix);
        }


    /* check for optional 0 or 0x */

    if (ix < fieldwidth && ch == '0')
        {
        GET_CHAR (ch, ix);

        if ((ix < fieldwidth) &&
            (ch == 'x' || ch == 'X') &&
            (base == 16 || base == 0))
            {
            base = 16;
            GET_CHAR (ch, ix);
            }
        else if (base == 0)
            base = 8;
        }
    else
        {
        /* default base is 10 */
        if (base == 0)
            base = 10;
        }


    /* scan digits */

    while (ch != EOF && ix < fieldwidth)
        {
        if (isdigit (ch))
            dig = ch - '0';
        else if (islower (ch))
            dig = ch - 'a' + 10;
        else if (isupper (ch))
            dig = ch - 'A' + 10;
        else
            break;

        if (dig >= base)
            break;

        num = (num * base) + dig;

        GET_CHAR (ch, ix);
        }


    /* check that we scanned at least one character */

    if (ix == 0)
        return (FALSE);


    /* return value to caller */

    if (neg)
        num = -num;

    if (pReturn != NULL)
        {
        switch (returnSpec)
            {
            case 'h': *((short *) pReturn) = num; break;
            case 'l': *((long *) pReturn)  = num; break;
            case (ll) : *((long long *) pReturn) = num; break;
            default:  *((int *) pReturn)   = num; break;
            }
        }

    *pCh = ch;
    *pNchars += ix;

    return (ix != 0);
    }

